Serverless Backend Architecture

**Referenced Files in This Document** - [wrangler.jsonc](file://wrangler.jsonc) - [worker.js](file://worker.js) - [package.json](file://package.json) - [.eleventy.js](file://.eleventy.js) - [cloudflare-pages.toml](file://cloudflare-pages.toml) - [netlify.toml](file://netlify.toml) - [src/alliance-login.njk](file://src/alliance-login.njk) - [src/alliance-members.njk](file://src/alliance-members.njk) - [src/services/polling.njk](file://src/services/polling.njk) - [src/_data/polling.js](file://src/_data/polling.js)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Security Architecture
  9. Deployment Configuration
  10. Static Site Delivery Model
  11. Monitoring, Logging, and Debugging
  12. Fallback Mechanisms and Error Handling
  13. Conclusion

Introduction

This document describes the Cloudflare Workers serverless backend for the Invest Australia Alliance platform. It covers the API route architecture for member authentication, CMS authentication, and data endpoints, along with session management using Cloudflare KV namespaces, integration with static site generation, security controls, deployment via Wrangler, and operational observability.

Project Structure

The project combines an Eleventy-based static site generator with a Cloudflare Worker that intercepts specific routes to provide authentication, CMS OAuth, and live data APIs, while delegating all other traffic to static asset delivery.

graph TB
subgraph "Build Pipeline"
ELEVENTY[".eleventy.js<br/>Builds _site/"]
DATA["src/_data/*<br/>Runtime data"]
TPL["src/*.njk<br/>Templates"]
end
subgraph "Cloudflare Worker"
WRK["worker.js<br/>fetch handler"]
ASSETS["ASSETS binding<br/>_site/"]
KV1["KV: MEMBER_EMAILS"]
KV2["KV: MAGIC_TOKENS"]
end
subgraph "External Services"
RES["Resend API"]
GH["GitHub OAuth"]
SHEETS["Google Sheets API"]
end
ELEVENTY --> ASSETS
DATA --> ELEVENTY
TPL --> ELEVENTY
ASSETS --> WRK
WRK --> KV1
WRK --> KV2
WRK --> RES
WRK --> GH
WRK --> SHEETS

Diagram sources

  • [.eleventy.js:267-283](file://.eleventy.js#L267-L283)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [worker.js:301-320](file://worker.js#L301-L320)

Section sources

  • [.eleventy.js:267-283](file://.eleventy.js#L267-L283)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [package.json:5-12](file://package.json#L5-L12)

Core Components

  • Worker entrypoint and routing logic
  • Member authentication endpoints
  • CMS OAuth endpoints for Sveltia
  • Live polling data endpoint
  • Static asset delivery via ASSETS binding
  • KV-backed session and token storage

Section sources

  • [worker.js:301-320](file://worker.js#L301-L320)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)

Architecture Overview

The Worker acts as a router:

  • Authenticated routes: /alliance/login, /alliance/verify, /alliance/logout, /alliance/members/*
  • CMS OAuth: /api/auth, /api/auth/callback
  • Public data: /api/polling.json
  • Everything else: served from ASSETS (static site)
sequenceDiagram
participant U as "User Agent"
participant W as "Worker (worker.js)"
participant A as "ASSETS (_site)"
participant K1 as "KV : MEMBER_EMAILS"
participant K2 as "KV : MAGIC_TOKENS"
participant R as "Resend API"
participant G as "GitHub OAuth"
participant S as "Google Sheets"
U->>W : Request /alliance/members/*
W->>W : Extract cookie
alt Cookie present
W->>W : Verify signature/expiry
alt Valid
W->>A : Fetch static page
A-->>U : 200 HTML
else Invalid/Expired
W-->>U : 302 Redirect to /alliance/login?next=...
end
else No cookie
W-->>U : 302 Redirect to /alliance/login?next=...
end
U->>W : POST /alliance/login (magic-link)
W->>K1 : Lookup member : <email>
alt Approved
W->>K2 : Store token : <hex>=email (TTL)
W->>R : Send magic-link email
end
W-->>U : 302 Redirect to /alliance/login?sent=1
U->>W : GET /alliance/verify?token=<hex>
W->>K2 : Get token : <hex>
alt Found
W->>K2 : Delete token : <hex>
W-->>U : 302 Redirect to /alliance/members/ with session cookie
else Not found/expired
W-->>U : 302 Redirect to /alliance/login?error=expired
end
U->>W : GET /api/polling.json?state=(federal|sa)
W->>S : Fetch spreadsheet range
S-->>W : JSON values
W-->>U : 200 JSON with CORS/cache headers
U->>W : GET /api/auth
W->>G : Redirect to authorize
U->>W : GET /api/auth/callback?code=...
W->>G : Exchange code for access token
W-->>U : HTML with postMessage to opener
U->>A : Other paths (e.g., /about, /services/*)
A-->>U : Static HTML/CSS/JS

Diagram sources

  • [worker.js:77-91](file://worker.js#L77-L91)
  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:233-276](file://worker.js#L233-L276)
  • [worker.js:193-227](file://worker.js#L193-L227)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)

Detailed Component Analysis

Member Authentication Endpoints

  • POST /alliance/login
    • Validates email via form submission
    • Checks membership against MEMBER_EMAILS KV
    • Generates a random hex token, stores in MAGIC_TOKENS KV with TTL
    • Sends magic-link email via Resend API
    • Always responds with the same “check your email” message to avoid enumeration
  • GET /alliance/verify
    • Reads token from query param
    • Retrieves email from MAGIC_TOKENS KV
    • Deletes the token immediately to prevent reuse
    • Issues a signed session cookie with expiry
    • Redirects to /alliance/members/
  • GET /alliance/logout
    • Clears the session cookie and redirects to login
  • GET /alliance/members/*
    • Middleware checks session cookie validity
    • On success, serves static content from ASSETS
    • On failure, redirects to login with next parameter
flowchart TD
Start(["POST /alliance/login"]) --> ReadForm["Read email + honeypot"]
ReadForm --> Honeypot{"Honeypot empty?"}
Honeypot --> |No| BotRedirect["Redirect to /alliance/login?sent=1"]
Honeypot --> |Yes| ValidateEmail["Validate email format"]
ValidateEmail --> Valid{"Valid?"}
Valid --> |No| ErrorEmail["Redirect to /alliance/login?error=email"]
Valid --> |Yes| CheckMember["Lookup member:<email> in MEMBER_EMAILS"]
CheckMember --> Approved{"Approved?"}
Approved --> |No| ShowSent["Still show 'check your email'"]
Approved --> |Yes| GenToken["Generate random token"]
GenToken --> StoreKV["Put token:<hex>=email TTL=900s"]
StoreKV --> SendEmail["POST https://api.resend.com/emails"]
SendEmail --> Done(["Redirect to /alliance/login?sent=1"])

Diagram sources

  • [worker.js:97-147](file://worker.js#L97-L147)

Section sources

  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:282-295](file://worker.js#L282-L295)
  • [worker.js:81-91](file://worker.js#L81-L91)

CMS Authentication (/api/auth)

  • Preflight OPTIONS returns CORS headers for acestrategies.au
  • GET /api/auth initiates GitHub OAuth authorization
  • GET /api/auth/callback exchanges code for access token and posts it to opener window for Sveltia CMS
sequenceDiagram
participant CMS as "Sveltia CMS"
participant W as "Worker"
participant GH as "GitHub OAuth"
CMS->>W : OPTIONS /api/auth
W-->>CMS : 200 CORS headers
CMS->>W : GET /api/auth
W->>GH : Redirect to authorize with client_id
GH-->>CMS : 302 to /api/auth/callback?code=...
CMS->>W : GET /api/auth/callback
W->>GH : POST /login/oauth/access_token
GH-->>W : {access_token}
W-->>CMS : HTML with postMessage(access_token)

Diagram sources

  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:193-198](file://worker.js#L193-L198)
  • [worker.js:200-227](file://worker.js#L200-L227)

Section sources

  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:193-198](file://worker.js#L193-L198)
  • [worker.js:200-227](file://worker.js#L200-L227)

Data Endpoint (/api/polling.json)

  • Validates presence of required environment variables for Google Sheets
  • Selects a sheet range based on state parameter
  • Returns structured JSON with cache headers suitable for edge caching
flowchart TD
Entry(["GET /api/polling.json"]) --> CheckEnv["Check GOOGLE_SHEETS_ID + API_KEY"]
CheckEnv --> EnvOK{"Both set?"}
EnvOK --> |No| Err503["Return {error: 'Service misconfigured'} 503"]
EnvOK --> |Yes| BuildRange["Select Federal SA ranges by state param"]
BuildRange --> FetchSheets["Fetch https://sheets.googleapis.com/.../values/<range>?key=..."]
FetchSheets --> Parse["Parse JSON values[0]"]
Parse --> BuildResp["Build pollingData object"]
BuildResp --> Return200["Return JSON with CORS + Cache-Control"]

Diagram sources

  • [worker.js:233-276](file://worker.js#L233-L276)

Section sources

  • [worker.js:233-276](file://worker.js#L233-L276)

Session Management with KV Namespaces

  • Session cookie name and duration are defined centrally
  • Session token format: base64(payload).signature
  • Payload includes email and expiry; signature computed with HMAC-SHA256 using SESSION_SECRET
  • Verification uses constant-time comparison to mitigate timing attacks
  • KV bindings:
    • MEMBER_EMAILS: key pattern "member:" with value "1"
    • MAGIC_TOKENS: key pattern "token:" with value "" and TTL 900s
classDiagram
class SessionToken {
+string payload
+string signature
+verify(secret) bool
}
class KVNamespace {
+get(key) string|null
+put(key, value, options)
+delete(key)
}
class Worker {
+handleLoginPost()
+handleVerify()
+handleMembersRoute()
+handleLogout()
}
Worker --> SessionToken : "creates/verifies"
Worker --> KVNamespace : "MEMBER_EMAILS/MAGIC_TOKENS"

Diagram sources

  • [worker.js:32-58](file://worker.js#L32-L58)
  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:81-91](file://worker.js#L81-L91)
  • [worker.js:282-295](file://worker.js#L282-L295)

Section sources

  • [worker.js:12-14](file://worker.js#L12-L14)
  • [worker.js:32-58](file://worker.js#L32-L58)
  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [worker.js:81-91](file://worker.js#L81-L91)
  • [worker.js:282-295](file://worker.js#L282-L295)

Dependency Analysis

  • Build-time dependencies (Eleventy, Pagefind, TinaCMS) produce static assets in _site/
  • Worker depends on:
    • ASSETS binding for static delivery
    • KV namespaces for membership and magic-link tokens
    • External APIs for email and OAuth
  • Deployment scripts orchestrate build and deploy via Wrangler
graph LR
P["package.json scripts"] --> E[".eleventy.js"]
E --> S["_site/"]
S --> W["worker.js"]
W --> A["ASSETS binding"]
W --> K1["MEMBER_EMAILS KV"]
W --> K2["MAGIC_TOKENS KV"]
W --> R["Resend API"]
W --> G["GitHub OAuth"]
W --> GS["Google Sheets"]

Diagram sources

  • [package.json:5-12](file://package.json#L5-L12)
  • [.eleventy.js:267-283](file://.eleventy.js#L267-L283)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [worker.js:301-320](file://worker.js#L301-L320)

Section sources

  • [package.json:5-12](file://package.json#L5-L12)
  • [.eleventy.js:267-283](file://.eleventy.js#L267-L283)
  • [wrangler.jsonc:9-12](file://wrangler.jsonc#L9-L12)
  • [worker.js:301-320](file://worker.js#L301-L320)

Performance Considerations

  • Static assets are served from Cloudflare’s edge via ASSETS binding, minimizing latency and bandwidth.
  • Polling API sets cache headers enabling edge caching and stale-while-revalidate for reduced origin load.
  • KV reads/writes are optimized for low-latency membership and token checks.
  • Constant-time verification avoids timing side-channels and keeps cryptographic checks efficient.

[No sources needed since this section provides general guidance]

Security Architecture

  • Token-based authentication:
    • Session cookies signed with HMAC-SHA256 using a secret stored as a Worker secret.
    • Tokens include expiry and are validated with constant-time comparison.
  • CSRF and XSS mitigations:
    • HttpOnly, Secure, SameSite=Lax session cookies.
    • Strict CSP-like headers applied by the static site configuration.
  • Rate limiting:
    • Not implemented in code; consider adding per-IP quotas at the edge or using Cloudflare WAF rules.
  • CORS:
    • Dedicated preflight handling for /api/auth.
    • Access-Control-Allow-Origin restricted to acestrategies.au for data endpoints.
  • Secrets management:
    • SESSION_SECRET, RESEND_API_KEY, GitHub OAuth client credentials are stored as secrets.
  • KV namespace configuration:
    • MEMBER_EMAILS and MAGIC_TOKENS must be bound in wrangler.jsonc prior to use.

Section sources

  • [worker.js:12-14](file://worker.js#L12-L14)
  • [worker.js:32-58](file://worker.js#L32-L58)
  • [worker.js:183-191](file://worker.js#L183-L191)
  • [worker.js:234-238](file://worker.js#L234-L238)
  • [wrangler.jsonc:23-26](file://wrangler.jsonc#L23-L26)
  • [wrangler.jsonc:28-34](file://wrangler.jsonc#L28-L34)

Deployment Configuration

  • Name and compatibility date define runtime behavior.
  • Main entrypoint is worker.js.
  • ASSETS binding points to _site directory produced by Eleventy.
  • Observability enabled.
  • KV namespaces and secrets are declared for member auth and external integrations.
  • Legacy Cloudflare Pages configuration is preserved for reference.
flowchart TD
Dev["wrangler dev"] --> Build["npm run build"]
Build --> Site["_site/"]
Site --> Deploy["wrangler deploy"]
Deploy --> Edge["Cloudflare Edge Network"]

Diagram sources

  • [package.json:11-12](file://package.json#L11-L12)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)

Section sources

  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
  • [package.json:11-12](file://package.json#L11-L12)
  • [cloudflare-pages.toml:1-17](file://cloudflare-pages.toml#L1-L17)

Static Site Delivery Model

  • Eleventy builds the static site into _site/.
  • Cloudflare Worker forwards non-auth routes to ASSETS for immediate edge delivery.
  • Member-protected pages are gated by session validation; successful sessions serve protected content from ASSETS.
  • Frontend templates integrate with the backend via:
    • Login form posting to /alliance/login
    • Protected dashboard rendering from /alliance/members
    • Polling data fetched from /api/polling.json
graph TB
subgraph "Frontend Templates"
L["alliance-login.njk"]
M["alliance-members.njk"]
P["services/polling.njk"]
end
subgraph "Static Content"
S1["_site/alliance/login/index.html"]
S2["_site/alliance/members/index.html"]
S3["_site/services/polling/index.html"]
end
L --> S1
M --> S2
P --> S3

Diagram sources

  • [src/alliance-login.njk:21](file://src/alliance-login.njk#L21)
  • [src/alliance-members.njk:14](file://src/alliance-members.njk#L14)
  • [src/services/polling.njk:6](file://src/services/polling.njk#L6)

Section sources

  • [src/alliance-login.njk:21](file://src/alliance-login.njk#L21)
  • [src/alliance-members.njk:14](file://src/alliance-members.njk#L14)
  • [src/services/polling.njk:6](file://src/services/polling.njk#L6)

Monitoring, Logging, and Debugging

  • Observability:
    • Enabled in wrangler.jsonc; leverage Cloudflare Logs and Metrics dashboards.
  • Local development:
    • Use wrangler dev to preview and test routes locally.
  • Debugging tips:
    • Inspect cookies and headers in browser devtools.
    • Verify KV bindings and secrets are set in the Cloudflare dashboard.
    • Confirm Resend and GitHub OAuth credentials are correctly configured.

Section sources

  • [wrangler.jsonc:6-8](file://wrangler.jsonc#L6-L8)
  • [package.json:12](file://package.json#L12)

Fallback Mechanisms and Error Handling

  • KV namespaces not configured:
    • Returns 503 with guidance to configure MEMBER_EMAILS and MAGIC_TOKENS.
  • Missing or invalid parameters:
    • Redirects with appropriate query params for user feedback.
  • Expired or reused tokens:
    • Redirects to login with error hints.
  • Misconfigured data endpoints:
    • Returns 503 for missing Google Sheets credentials.
  • Graceful degradation:
    • Non-auth routes fall back to ASSETS; static content remains available even if auth endpoints fail.

Section sources

  • [worker.js:70-75](file://worker.js#L70-L75)
  • [worker.js:111-113](file://worker.js#L111-L113)
  • [worker.js:158-159](file://worker.js#L158-L159)
  • [worker.js:244-246](file://worker.js#L244-L246)

Conclusion

The system leverages Cloudflare Workers to provide a secure, fast, and scalable backend for member authentication, CMS integration, and live data delivery, while serving the majority of content statically from the edge. KV namespaces enable lightweight session and token management, and the Worker’s routing ensures that only protected routes are intercepted, keeping the static site delivery model efficient and resilient.